/**
 * Copyright 2020 Huawei Technologies Co., Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ops_json_file.h"

#include "graph/types.h"
#include "util/log.h"
#include "util/util.h"

using namespace std;
using namespace nlohmann;
using namespace ::aicpu::FWKAdapter;

namespace {
const int kMaxWorkspaceSize = 1 * 1024 * 1024;
}

namespace aicpu {
OpsJsonFile &OpsJsonFile::Instance() {
  static OpsJsonFile instance;
  return instance;
}

bool OpsJsonFile::ParseUnderPath(const string &file_path, json &json_read) {
  aicpu::State ret = ReadJsonFile(file_path, json_read);
  if (ret.state != ge::SUCCESS) {
    return false;
  }

  return ConvertJsonFormat(json_read);
}

bool OpsJsonFile::ConvertJsonFormat(json &json_read) {
  AICPUE_LOGI("Start convert kernel json.");
  json op_infos = json::array();
  json json_null;
  for (auto it = json_read.cbegin(); it != json_read.cend(); ++it) {
    json new_json = it.value();
    string op_name = it.key();
    new_json[kKernelConfigOpName] = op_name;

    json in_output_format;
    json in_output_type;
    json in_output_real_name;
    json in_output_src_type;
    json in_output_dst_type;
    bool ret = ParseInputOutput(it.value(), in_output_format, in_output_type,
                                in_output_real_name, in_output_src_type, in_output_dst_type);

    AICPU_IF_BOOL_EXEC((!ret),
        AICPU_REPORT_CALL_ERROR("Call OpsJsonFile::ParseInputOutput failed, op[%s].",
            op_name.c_str());
        return false)
    new_json[kKernelConfigOpInfo][kKernelConfigFormat] = in_output_format;
    new_json[kKernelConfigOpInfo][kKernelConfigDataType] = in_output_type;
    new_json[kKernelConfigOpInfo][kKernelConfigName] = in_output_real_name;
    new_json[kKernelConfigOpInfo][kKernelConfigSrcType] = in_output_src_type;
    new_json[kKernelConfigOpInfo][kKernelConfigDstType] = in_output_dst_type;

    // compute cost
    int compute_cost = 0;
    auto buff = new_json[kKernelConfigOpInfo][kKernelConfigComputeCost];
    AICPU_IF_BOOL_EXEC((!CheckAndGetComputeCost(buff, op_name, compute_cost)),
                       return false);
    new_json[kKernelConfigOpInfo][kKernelConfigComputeCost] = compute_cost;

    // flag async
    bool flag_async = false;
    buff = new_json[kKernelConfigOpInfo][kKernelConfigFlagAsync];
    AICPU_IF_BOOL_EXEC(
        (!CheckAndGetBoolValue(buff, op_name, "flagAsync", flag_async)),
        return false);
    new_json[kKernelConfigOpInfo][kKernelConfigFlagAsync] = flag_async;

    // flag partial
    bool flag_partial = false;
    buff = new_json[kKernelConfigOpInfo][kKernelConfigFlagPartial];
    AICPU_IF_BOOL_EXEC(
        (!CheckAndGetBoolValue(buff, op_name, "flagPartial", flag_partial)),
        return false);
    new_json[kKernelConfigOpInfo][kKernelConfigFlagPartial] = flag_partial;

    // format agnostic
    bool format_agnostic = false;
    buff = new_json[kKernelConfigOpInfo][kKernelConfigFormatAgnostic];
    AICPU_IF_BOOL_EXEC((!CheckAndGetFormatAgnostic(
                           buff, op_name, "formatAgnostic", format_agnostic)),
                       return false);
    new_json[kKernelConfigOpInfo][kKernelConfigFormatAgnostic] =
        format_agnostic;

    // ops flag
    string ops_flag;
    buff = new_json[kKernelConfigOpInfo][kKernelConfigOpsFlag];
    AICPU_IF_BOOL_EXEC((!CheckAndGetOpsFlag(buff, op_name, ops_flag)),
                       return false);
    new_json[kKernelConfigOpInfo][kKernelConfigOpsFlag] = ops_flag;

    // shape type
    int shape_type = 0;
    buff = new_json[kKernelConfigOpInfo][kKernelConfigShapeType];
    AICPU_IF_BOOL_EXEC((!CheckAndGetShapeType(buff, op_name, shape_type)),
                       return false);
    new_json[kKernelConfigOpInfo][kKernelConfigShapeType] = shape_type;

    // workspace
    int workspace_size = 0;
    buff = new_json[kKernelConfigOpInfo][kKernelConfigWorkspaceSize];
    AICPU_IF_BOOL_EXEC((!CheckAndGetWorkspaceSize(buff, op_name, workspace_size)),
                       return false);
    new_json[kKernelConfigOpInfo][kKernelConfigWorkspaceSize] = workspace_size;

    // user defined: AICPUKernel only
    bool user_defined = false;
    buff = new_json[kKernelConfigOpInfo][kKernelConfigUserDefined];
    AICPU_IF_BOOL_EXEC((!CheckAndGetNonessentialBoolValue(
                           buff, op_name, "userDefined", user_defined)),
                       return false);
    new_json[kKernelConfigOpInfo][kKernelConfigUserDefined] = user_defined;

    // function name: AICPUKernel only
    buff = new_json[kKernelConfigOpInfo][kKernelConfigFunctionName];
    AICPU_IF_BOOL_EXEC(
        (buff == json_null),
        new_json[kKernelConfigOpInfo][kKernelConfigFunctionName] = "";);

    // kernel so: AICPUKernel only
    buff = new_json[kKernelConfigOpInfo][kKernelConfigKernelSo];
    AICPU_IF_BOOL_EXEC(
        (buff == json_null),
        new_json[kKernelConfigOpInfo][kKernelConfigKernelSo] = "";);

    // ops topic type

    FWKAdapter::FWKExtTopicType topic_type;
    buff = new_json[kKernelConfigOpInfo][kKernelConfigTopicType];
    AICPU_IF_BOOL_EXEC((!CheckAndGetTopicType(buff, op_name, topic_type)), return false);
    new_json[kKernelConfigOpInfo][kKernelConfigTopicType] = topic_type;

    op_infos.push_back(new_json);
  }

  json_read = {};
  json_read[kKernelConfigLibName] = kKernelConfigTfKernel;
  AICPU_IF_BOOL_EXEC(
      !op_infos.empty(),
      json_read[kKernelConfigLibName] =
          op_infos[0][kKernelConfigOpInfo][kKernelConfigKernelLib];);
  json_read[kKernelConfigOpInfos] = op_infos;

  std::string kernel_lib = json_read[kKernelConfigLibName];
  AICPUE_LOGI("Convert json success, kernel config is %s.", kernel_lib.c_str());
  return true;
}

bool OpsJsonFile::ParseInputOutput(const json &json_read, json &format_json,
                                   json &type_json, json &name_json,
                                   nlohmann::json &src_type_json,
                                   nlohmann::json &dst_type_json) {
  json format_result;
  json type_result;
  json name_result;
  json src_type_result;
  json dst_type_result;
  const string input_str = "input";
  const string output_str = "output";
  const string dynamic_input_str = "dynamic_input";
  const string dynamic_output_str = "dynamic_output";
  for (json::const_iterator iter = json_read.cbegin(); iter != json_read.cend();
       iter++) {
    const string key = iter.key();
    // json_read maybe include key inputn or outputn (n is a number satisfied n
    // >=0,such as input0, output2,...) this judgement ensures that the next
    // std::strncmp() can be executed correctly
    if (key.size() < output_str.size()) {
      continue;
    }

    if ((strncmp(key.c_str(), input_str.c_str(), input_str.size()) == 0) ||
        (strncmp(key.c_str(), output_str.c_str(), output_str.size()) == 0) ||
        (strncmp(key.c_str(), dynamic_input_str.c_str(),
                 dynamic_input_str.size()) == 0) ||
        (strncmp(key.c_str(), dynamic_output_str.c_str(),
                 dynamic_output_str.size()) == 0)) {
      json json_key = json_read[key];
      auto iter_format = json_key.find(kKernelConfigFormat);

      if (iter_format != json_key.end()) {
        string format = iter_format.value().get<string>();
        AICPU_IF_BOOL_EXEC(
            (format != "ND") && (format != "NHWC") && (format != "NCHW"),
            AICPU_REPORT_INNER_ERROR(
              "Invalid format[%s], should be ND, NHWC or NCHW.", format.c_str());
            return false)
        format_result[key] = format;
      }
      auto iter_type = json_key.find(kKernelConfigDataType);
      if (iter_type != json_key.end()) {
        string type = iter_type.value().get<string>();
        type_result[key] = type;
      }
      auto iter_name = json_key.find(kKernelConfigName);
      if (iter_name != json_key.end()) {
        string name = iter_name.value().get<string>();
        name_result[key] = name;
      }
      auto iter_src_type = json_key.find(kKernelConfigSrcType);
      if (iter_src_type != json_key.end()) {
        string src_type = iter_src_type.value().get<string>();
        src_type_result[key] = src_type;
      }
      auto iter_dst_type = json_key.find(kKernelConfigDstType);
      if (iter_dst_type != json_key.end()) {
        string dst_type = iter_dst_type.value().get<string>();
        dst_type_result[key] = dst_type;
      }
    }
  }
  format_json = format_result;
  type_json = type_result;
  name_json = name_result;
  src_type_json = src_type_result;
  dst_type_json = dst_type_result;
  return true;
}

bool OpsJsonFile::CheckAndGetComputeCost(const json &buff, const string &op_name,
                                         int &compute_cost) {
  if (!buff.empty()) {
    aicpu::State state = StringToNum(buff.get<string>(), compute_cost);
    if (state.state != ge::SUCCESS) {
        AICPU_REPORT_INNER_ERROR("Convert %s[%s] to int for op[%s] failed, %s.",
            buff.get<string>().c_str(), kKernelConfigComputeCost.c_str(),
            op_name.c_str(), state.msg.c_str());
        return false;
    }
    return true;
  } else {
    AICPU_REPORT_INNER_ERROR("[%s] is empty, op[%s].",
        kKernelConfigComputeCost.c_str(), op_name.c_str());
    return false;
  }
}

bool OpsJsonFile::CheckAndGetOpsFlag(const json &buff, const string &op_name,
                                     string &ops_flag) {
  if (!buff.empty()) {
    ops_flag = buff.get<string>();
    int is_open = ops_flag.compare("OPS_FLAG_OPEN");
    int is_close = ops_flag.compare("OPS_FLAG_CLOSE");
    if ((is_open != 0) && (is_close != 0)) {
      AICPU_REPORT_INNER_ERROR("Invalid op_info.opsFlag[%s], should be "
          "OPS_FLAG_OPEN or OPS_FLAG_CLOSE, op[%s].",
          ops_flag.c_str(), op_name.c_str());
      return false;
    }
    if (is_open == 0) {
      ops_flag = kOpsFlagOpen;
    } else {
      ops_flag = kOpsFlagClose;
    }
  } else {
    AICPUE_LOGI("Read kernel json file, op[%s], op_info.opsFlag is empty.",
                op_name.c_str());
    ops_flag = kOpsFlagEmpty;
  }
  return true;
}

void OpsJsonFile::ConvertTopicType(const std::string &topic_type_str,
                                   FWKExtTopicType &topic_type) {
  topic_type = FWK_ADPT_TOPIC_INVALID;
  if (topic_type_str == kTopicTypeDeviceOnly) {
    topic_type = FWK_ADPT_TOPIC_DEVICE_ONLY;
  } else if (topic_type_str == kTopicTypeDeviceFirst) {
    topic_type = FWK_ADPT_TOPIC_DEVICE_FIRST;
  } else if (topic_type_str == kTopicTypeHostOnly) {
    topic_type = FWK_ADPT_TOPIC_HOST_ONLY;
  } else if (topic_type_str == kTopicTypeHostFirst) {
    topic_type = FWK_ADPT_TOPIC_HOST_FIRST;
  }
}

bool OpsJsonFile::CheckAndGetTopicType(const nlohmann::json &buff,
                                       const std::string &op_name,
                                       FWKExtTopicType &topic_type) {
  if (!buff.empty()) {
    ConvertTopicType(buff.get<string>(), topic_type);
    AICPUE_LOGI("Read kernel json file, opName:[%s], topicType:[%d].", op_name.c_str(), topic_type);
    if (topic_type == FWK_ADPT_TOPIC_INVALID) {
      AICPU_REPORT_INNER_ERROR("Read kernel json file, opName:[%s], topicType:[%d] is invalid, "
          "only support [DEVICE_ONLY/DEVICE_FIRST/HOST_ONLY/HOST_FIRST].",
          op_name.c_str(), topic_type);
      return false;
    }
    return true;
  }
  topic_type = FWK_ADPT_TOPIC_DEVICE_ONLY;

  return true;
}

bool OpsJsonFile::CheckAndGetBoolValue(const json &buff, const string &op_name,
                                       const string &field_str, bool &value) {
  if (!buff.empty()) {
    aicpu::State state = StringToBool(buff.get<string>(), value);
    if (state.state == ge::SUCCESS) {
      return true;
    } else {
      AICPU_REPORT_INNER_ERROR(
          "invalid op_info.%s[%s], should be False or True, op[%s].",
          field_str.c_str(), buff.get<string>().c_str() ,op_name.c_str());
      return false;
    }
  } else {
    AICPU_REPORT_INNER_ERROR("op_info.%s is empty, op[%s].",
        field_str.c_str(), op_name.c_str());
    return false;
  }
}

// check and get shape type field from kernel info
bool OpsJsonFile::CheckAndGetShapeType(const json &buff, const string &op_name,
                                       int &shape_type) {
  if (!buff.empty()) {
    aicpu::State state = StringToNum(buff.get<string>(), shape_type);
    if (state.state != ge::SUCCESS) {
        AICPU_REPORT_INNER_ERROR("Convert %s[%s] to int for op[%s] failed, %s.",
            buff.get<string>().c_str(), kKernelConfigShapeType.c_str(),
            op_name.c_str(), state.msg.c_str());
        return false;
    }
    if ((shape_type < static_cast<int>(ge::DEPEND_IN_SHAPE)) ||
        (shape_type > static_cast<int>(ge::DEPEND_COMPUTE))) {
      AICPU_REPORT_INNER_ERROR(
          "invalid shape type[%d], should be in[%d, %d], op[%s].", shape_type,
          static_cast<int>(ge::DEPEND_IN_SHAPE),
          static_cast<int>(ge::DEPEND_COMPUTE), op_name.c_str());
      return false;
    }
  } else {
    AICPUE_LOGW(
        "Read kernel json file, op[%s], op_info.shapeType is empty, use "
        "default.",
        op_name.c_str());
    shape_type = ge::DEPEND_IN_SHAPE;
  }
  return true;
}

bool OpsJsonFile::CheckAndGetNonessentialBoolValue(const json &buff,
                                                   const string &op_name,
                                                   const string &field_str,
                                                   bool &value) {
  if (!buff.empty()) {
    if (StringToBool(buff.get<string>(), value).state != ge::SUCCESS) {
      AICPU_REPORT_INNER_ERROR(
          "invalid op_info.%s[%s], should be False or True, op[%s].",
          field_str.c_str(), buff.get<string>().c_str(), op_name.c_str());
      return false;
    }
  }
  return true;
}

bool OpsJsonFile::CheckAndGetFormatAgnostic(const json &buff,
                                            const string &op_name,
                                            const string &field_str,
                                            bool &value) {
  if (!buff.empty()) {
    aicpu::State state = StringToBool(buff.get<string>(), value);
    if (state.state == ge::SUCCESS) {
      return true;
    } else {
      AICPU_REPORT_CALL_ERROR("Call StringToBool failed, %s. [%s] must be False"
          " or True in op info store file, op[%s]",
          state.msg.c_str(), field_str.c_str(), op_name.c_str());
      return false;
    }
  } else {
    AICPUE_LOGW("Read kernel json file, op[%s], op_info.%s is empty.",
                op_name.c_str(), field_str.c_str());
    return true;
  }
}

// check and get workspace size from kernel info
bool OpsJsonFile::CheckAndGetWorkspaceSize(const nlohmann::json &buff,
                                           const std::string &op_name,
                                           int &workspace_size) {
  if (!buff.empty()) {
    aicpu::State state = StringToNum(buff.get<string>(), workspace_size);
    if (state.state != ge::SUCCESS) {
        AICPU_REPORT_INNER_ERROR("Convert %s[%s] to int for op[%s] failed, %s.",
            kKernelConfigWorkspaceSize.c_str(), buff.get<string>().c_str(),
            op_name.c_str(), state.msg.c_str());
        return false;
    }
    if ((workspace_size < 0) || (workspace_size > kMaxWorkspaceSize)) {
      AICPU_REPORT_INNER_ERROR("invalid %s[%d] should be in (0, %d). op[%s]",
          kKernelConfigWorkspaceSize.c_str(), workspace_size, kMaxWorkspaceSize,
          op_name.c_str());
      return false;
    }
  } else {
    AICPUE_LOGW(
        "Read kernel json file, op[%s], op_info.workspaceSize is empty.",
        op_name.c_str());
    workspace_size = 0;
  }
  return true;
}

template <typename T>
inline void Assignment(T &varible, const string &key, const json &json_read) {
  auto iter = json_read.find(key);
  if (iter != json_read.end()) {
    varible = iter.value().get<T>();
  }
}

void from_json(const json &json_read, OpInfoDescs &infos) {
  Assignment(infos.opInfos, kKernelConfigOpInfos, json_read);
  Assignment(infos.libName, kKernelConfigLibName, json_read);
}

void from_json(const json &json_read, OpFullInfo &op_info) {
  Assignment(op_info.engine, kKernelConfigEngine, json_read);

  Assignment(op_info.opKernelLib, kKernelConfigKernelLib, json_read);

  Assignment(op_info.computeCost, kKernelConfigComputeCost, json_read);

  Assignment(op_info.flagPartial, kKernelConfigFlagPartial, json_read);

  Assignment(op_info.flagAsync, kKernelConfigFlagAsync, json_read);

  op_info.workspaceSize = 0;
  Assignment(op_info.workspaceSize, kKernelConfigWorkspaceSize, json_read);

  // param[userDefined] only in aicpu_kernel.json
  op_info.userDefined = false;
  Assignment(op_info.userDefined, kKernelConfigUserDefined, json_read);

  // param[kernelSo] only in aicpu_kernel.json
  op_info.kernelSo = "";
  Assignment(op_info.kernelSo, kKernelConfigKernelSo, json_read);

  // param[functionName] only in aicpu_kernel.json
  op_info.functionName = "";
  Assignment(op_info.functionName, kKernelConfigFunctionName, json_read);

  Assignment(op_info.formatAgnostic, kKernelConfigFormatAgnostic, json_read);

  Assignment(op_info.opsFlag, kKernelConfigOpsFlag, json_read);

  Assignment(op_info.shapeType, kKernelConfigShapeType, json_read);

  Assignment(op_info.topicType, kKernelConfigTopicType, json_read);

  // param[inOutputFormat]
  auto iter = json_read.find(kKernelConfigFormat);
  if (iter != json_read.end()) {
    json format_json = iter.value();
    for (json::iterator it = format_json.begin(); it != format_json.end(); it++) {
      string in_output_name = it.key();
      string in_output_format = it.value().get<string>();
      op_info.inOutFormat[in_output_name] = in_output_format;
    }
  }

  // param[inOutDataType]
  iter = json_read.find(kKernelConfigDataType);
  if (iter != json_read.end()) {
    json data_type_json = iter.value();
    for (json::iterator it = data_type_json.begin(); it != data_type_json.end();
         it++) {
      string in_output_name = it.key();
      string in_output_data_type = it.value().get<string>();
      op_info.inOutDataType[in_output_name] = in_output_data_type;
    }
  }

  // param[inOutName]
  iter = json_read.find(kKernelConfigName);
  if (iter != json_read.end()) {
    json name_json = iter.value();
    for (json::iterator it = name_json.begin(); it != name_json.end(); it++) {
      string in_output_name = it.key();
      string in_output_real_name = it.value().get<string>();
      op_info.inOutRealName[in_output_real_name] = in_output_name;
    }
  }

  // param[castSrcType]
  iter = json_read.find(kKernelConfigSrcType);
  if (iter != json_read.end()) {
    json src_type_json = iter.value();
    for (json::iterator it = src_type_json.begin(); it != src_type_json.end();
         it++) {
      string input_name = it.key();
      string input_src_type = it.value().get<string>();
      op_info.castSrcType[input_name] = input_src_type;
    }
  }

  // param[castDstType]
  iter = json_read.find(kKernelConfigDstType);
  if (iter != json_read.end()) {
    json dst_type_json = iter.value();
    for (json::iterator it = dst_type_json.begin(); it != dst_type_json.end();
         it++) {
      string input_name = it.key();
      string input_dst_type = it.value().get<string>();
      op_info.castDstType[input_name] = input_dst_type;
    }
  }
}

void from_json(const json &json_read, OpInfoDesc &desc) {
  Assignment(desc.opName, kKernelConfigOpName, json_read);
  Assignment(desc.opInfo, kKernelConfigOpInfo, json_read);
}
}  // namespace aicpu
